package com.lincoln.redis.server.config;

import com.lincoln.redis.server.StaticValue;
import com.lincoln.redis.server.entity.User;
import com.lincoln.redis.server.service.UserService;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.bytecode.CodeAttribute;
import javassist.bytecode.LocalVariableAttribute;
import javassist.bytecode.MethodInfo;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;

/**
 * 对Service中的方法执行前new jedis,执行后关闭
 *
 * @author lincoln
 */
@Aspect
@Configuration
public class JedisAopConfiguration {
    @Autowired
    JedisPool jedisPool;
    @Autowired
    UserService userService;

    /**
     * 实例化jedis
     *
     * @param joinPoint
     * @throws Throwable
     */
    void instantiationJedis(JoinPoint joinPoint, Class clazz) throws Throwable {
        Object that = joinPoint.getTarget();
        Field field = clazz.getDeclaredField("jedis");
        if (field == null) {
            return;
        }
        field.setAccessible(true);
        field.set(that, jedisPool.getResource());
        Jedis jedis = ((Jedis) field.get(that));
        if (jedis != null) {
            jedis.connect();
        }
    }

    /**
     * 销毁jedis
     *
     * @param joinPoint
     * @throws Throwable
     */
    void destroy(JoinPoint joinPoint, Class clazz) throws Throwable {
        Object that = joinPoint.getTarget();
        Field field = clazz.getDeclaredField("jedis");
        field.setAccessible(true);
        if (field == null || field.get(that) == null) {
            return;
        }
        Jedis jedis = ((Jedis) field.get(that));
        if (jedis != null) {
            jedis.disconnect();
            jedis.close();
        }
    }

    /**
     * 对com.lincoln.redis.server.service.impl包下的所有ServiceImpl结尾的类的所有方法定义切点
     */
    @Pointcut("execution(public * com.lincoln.redis.server.service.impl.*ServiceImpl.*(..))")
    public void cutServiceFun() {
    }

    /**
     * 执行方法前,新建一个连接
     *
     * @param joinPoint
     * @throws Throwable
     */
    @Before("cutServiceFun()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {
        if (joinPoint.getSignature().getName().startsWith("ex")) {
            return;
        }
        instantiationJedis(joinPoint, joinPoint.getTarget().getClass().getSuperclass());
    }

    /**
     * 执行方法后,关闭连接
     *
     * @param joinPoint
     * @throws Throwable
     */
    @After("cutServiceFun()")
    public void doAfterReturning(JoinPoint joinPoint) throws Throwable {
        if (joinPoint.getSignature().getName().startsWith("ex")) {
            return;
        }
        destroy(joinPoint, joinPoint.getTarget().getClass().getSuperclass());
    }

    /**
     * 对com.lincoln.redis.server.controller.RedisController所有方法定义切点
     */
    @Pointcut("execution(public com.lincoln.bean.ApiCode com.lincoln.redis.server.controller.RedisController.*(..))")
    public void cutRedisController() {
    }

    /**
     * 执行方法前,新建一个连接
     *
     * @param joinPoint
     * @throws Throwable
     */
    @Before("cutRedisController()")
    public void before(JoinPoint joinPoint) throws Throwable {
        instantiationJedis(joinPoint, joinPoint.getTarget().getClass());
    }

    @Around("cutRedisController()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        //处理所有的key参数
        if (((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest().getParameter("key") != null) {
            Object[] objects = joinPoint.getArgs();
            String classType = joinPoint.getTarget().getClass().getName();
            Class<?> clazz = Class.forName(classType);
            String clazzName = clazz.getName();
            String methodName = joinPoint.getSignature().getName();
            User user = userService.findByName((String) SecurityContextHolder.getContext().getAuthentication().getPrincipal());
            objects[indexParam(this.getClass(), clazzName, methodName, "key")] = StaticValue.USER_TABLE_HEADER + "." + user.getId() + "_" + user.getUsername() + "." + ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest().getParameter("key");
            Object ret = joinPoint.proceed(objects);
            return ret;
        }
        Object ret = joinPoint.proceed();
        return ret;
    }

    /**
     * 执行方法后,关闭连接
     *
     * @param joinPoint
     * @throws Throwable
     */
    @After("cutRedisController()")
    public void after(JoinPoint joinPoint) throws Throwable {
        destroy(joinPoint, joinPoint.getTarget().getClass());
    }

    /**
     * 对com.lincoln.redis.server.controller.RedisValueController
     */
    @Pointcut("execution(public com.lincoln.bean.ApiCode com.lincoln.redis.server.controller.RedisValueController.*(..))")
    public void cutRedisValueController() {
    }

    @Before("cutRedisValueController()")
    public void beforeRedisValueController(JoinPoint joinPoint) throws Throwable {
        instantiationJedis(joinPoint, joinPoint.getTarget().getClass());
    }

    @After("cutRedisValueController()")
    public void RedisValueController(JoinPoint joinPoint) throws Throwable {
        destroy(joinPoint, joinPoint.getTarget().getClass());
    }
    @Around("cutRedisValueController()")
    public Object aroundRedisValueController(ProceedingJoinPoint joinPoint) throws Throwable {
        //处理所有的key参数
        if (((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest().getParameter("key") != null) {
            Object[] objects = joinPoint.getArgs();
            String classType = joinPoint.getTarget().getClass().getName();
            Class<?> clazz = Class.forName(classType);
            String clazzName = clazz.getName();
            String methodName = joinPoint.getSignature().getName();
            User user = userService.findByName((String) SecurityContextHolder.getContext().getAuthentication().getPrincipal());
            objects[indexParam(this.getClass(), clazzName, methodName, "key")] = StaticValue.USER_TABLE_HEADER + "." + user.getId() + "_" + user.getUsername() + "." + ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest().getParameter("key");
            Object ret = joinPoint.proceed(objects);
            return ret;
        }
        Object ret = joinPoint.proceed();
        return ret;
    }

    /**
     * 获取字段名和字段值
     */
    private Map<String, Object> getFieldsName(Class cls, String clazzName, String methodName, Object[] args) throws Exception {
        Map<String, Object> map = new HashMap<String, Object>();
        ClassPool pool = ClassPool.getDefault();
        ClassClassPath classPath = new ClassClassPath(cls);
        pool.insertClassPath(classPath);
        CtClass cc = pool.get(clazzName);
        CtMethod cm = cc.getDeclaredMethod(methodName);
        MethodInfo methodInfo = cm.getMethodInfo();
        CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
        LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
        if (attr == null) {
            // exception
        }
        int pos = Modifier.isStatic(cm.getModifiers()) ? 0 : 1;
        for (int i = 0; i < cm.getParameterTypes().length; i++) {
            //paramNames即参数名
            map.put(attr.variableName(i + pos), args[i]);
        }
        return map;
    }

    private int indexParam(Class cls, String clazzName, String methodName, String paramName) throws Exception {
        Map<String, Object> map = new HashMap<String, Object>();
        ClassPool pool = ClassPool.getDefault();
        ClassClassPath classPath = new ClassClassPath(cls);
        pool.insertClassPath(classPath);
        CtClass cc = pool.get(clazzName);
        CtMethod cm = cc.getDeclaredMethod(methodName);
        MethodInfo methodInfo = cm.getMethodInfo();
        CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
        LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
        if (attr == null) {
            // exception
        }
        int pos = Modifier.isStatic(cm.getModifiers()) ? 0 : 1;
        for (int i = 0; i < cm.getParameterTypes().length; i++) {
            if (attr.variableName(i + pos).equals(paramName)) {
                return i;
            }
        }
        return -1;
    }
}
